// @Author EthanScriptOn
// @Desc
package initialization

import (
	"fmt"
	"sync"
	"sync/atomic"
)

// PositionTag Identifies the location of the initialization function
type PositionTag string

// InitTag Identifies the tag of the initialization function
type InitTag string

// OnceInitializer The initializer is loaded once,
// and the OnceInitializer has a one-to-one relationship with the initialization function
type OnceInitializer struct {
	positionTag PositionTag
	initTag     InitTag
	initFunc    InitializerFunc
	value       interface{}
	once        *sync.Once
	flag        *atomic.Value
	reset       chan struct{}
}

func GenerateOnceInitializer(positionTag PositionTag, initTag InitTag) *OnceInitializer {
	return &OnceInitializer{positionTag: positionTag, initTag: initTag, once: new(sync.Once), flag: new(atomic.Value), reset: make(chan struct{}, 1)}
}

// Set Set up an initialization function
func (o *OnceInitializer) Set(priority int, initFunc InitializerFunc) (err error) {
	err = AddInitializerFunc(o.positionTag, o.initTag, priority, initFunc, true)
	if err != nil {
		err = fmt.Errorf("an error occurred when adding positionTag as [%+v] and initTag as [%+v]. The error is [%+v]", o.positionTag, o.initTag, err)
	}
	return
}

// Get Get the result of the initialization function
func (o *OnceInitializer) Get() (instance interface{}, err error) {
	var receive = &err
	onceDo := func() {
		o.flag.Store(true)
		initializerFunc, initializerErr := GetInitializerFunc(o.positionTag, o.initTag)
		if initializerErr != nil {
			*receive = fmt.Errorf("according to [%v], searched for initFunc but couldn't find it in [%v] err is [%v]", o.initTag, o.positionTag, initializerErr)
			return
		}
		o.initFunc = initializerFunc
		funcRes, funcErr := o.initFunc()
		if funcErr != nil {
			*receive = funcErr
			return
		}
		o.value = funcRes
	}
	select {
	case _, ok := <-o.reset:
		if ok {
			onceDo()
		}
	default:
		o.once.Do(onceDo)
	}
	return o.value, *receive
}

// IsInitialized Whether the initialization function has been initialized
func (o *OnceInitializer) IsInitialized() bool {
	return o.flag.Load().(bool)
}

// Reset The initialization function is reloaded
func (o *OnceInitializer) Reset() {
	o.flag.Store(false)
	o.reset <- struct{}{}
}
